package parser;



import java.util.ArrayList;

import scanner.LexicalException;
import scanner.Scanner;
import scanner.Token;
import util.AST.AST;
import util.AST.Programa;
import util.AST.Comando.Comando;
import util.AST.Comando.ComandoFuncao;
import util.AST.Comando.ComandoPrograma;
import util.AST.Comando.ComandoSubRotina;
import util.AST.Comando.Simples.DeclaracaoVariavel;

/**
 * Parser class
 * @version 2010-august-29
 * @discipline Projeto de Compiladores
 * @author Gustavo H P Carvalho
 * @email gustavohpcarvalho@ecomp.poli.br
 */
public class ParserTEMPORARIO {

	// The current token
	private Token currentToken = null;
	// The scanner
	private Scanner scanner = null;
	
	/**
	 * Parser constructor
	 * @throws LexicalException 
	 */
	public ParserTEMPORARIO() throws LexicalException {
		// Initializes the scanner object
		this.scanner = new Scanner();
		this.currentToken = this.scanner.getNextToken();
	}
	
	/**
	 * Verifies if the source program is syntactically correct
	 * @throws SyntacticException
	 */ //TODO
	public AST parse() throws SyntacticException, LexicalException {
		Programa programa = parsePrograma();
		return programa;
	}
	
	
	public Programa parsePrograma() throws SyntacticException, LexicalException{
		//Lista principal
		ArrayList<Comando> comandos = new ArrayList<Comando>();
		//Listas Internas
		ArrayList<ComandoPrograma> comandosPrograma = new ArrayList<ComandoPrograma>();
		ArrayList<ComandoFuncao> comandosFuncao = new ArrayList<ComandoFuncao>();
		ArrayList<ComandoSubRotina> comandosSubrotina = new ArrayList<ComandoSubRotina>();
		
		//ArrayLists aninhadas
		while(this.currentToken.getKind()!=GrammarSymbols.EOT){
			if(this.currentToken.getKind()==GrammarSymbols.PROGRAMA){
				comandosPrograma = parseComandoPrograma();
				//parseComandoPrograma();
			}
			
			else if(this.currentToken.getKind()==GrammarSymbols.FUNCTION){
				comandos.add( (ComandoFuncao) parseComandoFuncao());
				//parseComandoFuncao();
			}
			
			else if(this.currentToken.getKind()==GrammarSymbols.SUBROUTINE)
				comandos.add( (ComandoSubRotina) parseComandoSubrotina());
				//parseComandoSubrotina();
			}
			accept(GrammarSymbols.EOT);
			return new Programa(comandos);
	}
	
	private ArrayList<ComandoPrograma> parseComandoPrograma() throws SyntacticException, LexicalException{
		
		ArrayList<ComandoPrograma> comandosPrograma = new ArrayList<ComandoPrograma>();
		
		if(this.currentToken.getKind()!=GrammarSymbols.EOT){
			
		accept(GrammarSymbols.PROGRAMA);
		accept(GrammarSymbols.ID);
		
		while(currentToken.getKind() != GrammarSymbols.END){
			comandosPrograma.add( (ComandoPrograma) parseComandoSimples());
			//parseComandoSimples();
		}
		accept(GrammarSymbols.END);
		accept(GrammarSymbols.PROGRAMA);
		}
		return comandosPrograma;
	}
	
	private Comando parseComandoSimples()throws LexicalException,SyntacticException{
		
		Comando comando = null;
		
		if(this.currentToken.getKind()==GrammarSymbols.TIPO_INTEIRO||this.currentToken.getKind()==GrammarSymbols.TIPO_LOGICO
				||this.currentToken.getKind()==GrammarSymbols.TIPO_REAL){
			comando = parseDeclaracaoVariavel();
			//parseDeclaracaoVariavel();
		}
		else if(this.currentToken.getKind()==GrammarSymbols.DO){
			acceptIt();
			parseComandoWhile();
			accept(GrammarSymbols.END);
			accept(GrammarSymbols.DO);
		}
		
		else if(this.currentToken.getKind()==GrammarSymbols.IF){
			
			acceptIt();
			parseExpressao();
			accept(GrammarSymbols.THEN);
			
			while(this.currentToken.getKind()!=GrammarSymbols.ELSE&&this.currentToken.getKind()!=GrammarSymbols.END){
				
				parseComandoSimples();
			}
			
			if(this.currentToken.getKind()==GrammarSymbols.ELSE){
				
				acceptIt();
				while(this.currentToken.getKind()!=GrammarSymbols.END){
					parseComandoSimples();
				}
			}
			
			
			accept(GrammarSymbols.END);
			accept(GrammarSymbols.IF);
			
		}
		
		else if(this.currentToken.getKind()==GrammarSymbols.CALL){
			parseChamadaSubrotina();
			
		}
		
		else if(this.currentToken.getKind()==GrammarSymbols.PRINT){
			acceptIt();
			accept(GrammarSymbols.MULTIPLICACAO);
			accept(GrammarSymbols.VIRGULA);
			
			if(this.currentToken.getKind()==GrammarSymbols.PONTO_VIRGULA){
				acceptIt();
			}
			else  if(this.currentToken.getKind()==GrammarSymbols.NUMERO_INTEIRO||this.currentToken.getKind()==GrammarSymbols.VALOR_LOGICO
				||this.currentToken.getKind()==GrammarSymbols.NUMERO_REAL || this.currentToken.getKind()==GrammarSymbols.STRING){
					acceptIt();
					while(this.currentToken.getKind()==GrammarSymbols.VIRGULA){
						acceptIt();
						parseExpressao();
					}
				}
			}else if(this.currentToken.getKind()==GrammarSymbols.ID){
					acceptIt();
					if(this.currentToken.getKind()==GrammarSymbols.PONTO_VIRGULA){
						acceptIt();
					}
					else if(this.currentToken.getKind()==GrammarSymbols.PARENTESE_ESQ){
							acceptIt();
							if(this.currentToken.getKind()!=GrammarSymbols.PARENTESE_DIR){
								parseParametros();
							}
					accept(GrammarSymbols.PARENTESE_DIR);
					accept(GrammarSymbols.PONTO_VIRGULA);
				}
				else parseAtribuicaoVariavel(); 
	}
		else parseAtribuicaoVariavel();
		return null;
	}

	private DeclaracaoVariavel parseDeclaracaoVariavel()throws LexicalException,SyntacticException{
		
		if(this.currentToken.getKind()==GrammarSymbols.TIPO_INTEIRO
				||this.currentToken.getKind()==GrammarSymbols.TIPO_REAL
				||this.currentToken.getKind()==GrammarSymbols.TIPO_LOGICO){
			
			acceptIt();
			accept(GrammarSymbols.DECLARACAO);
			accept(GrammarSymbols.ID);
			
			while(this.currentToken.getKind()!=GrammarSymbols.PONTO_VIRGULA){
				
				if(this.currentToken.getKind()==GrammarSymbols.VIRGULA){
					acceptIt();
					
					if(this.currentToken.getKind()==GrammarSymbols.PONTO_VIRGULA){
						
						throw new SyntacticException("Erro sintatico", this.currentToken);
					}
					else if(this.currentToken.getKind()==GrammarSymbols.ID){
						acceptIt();
					}	
			}
			
			else {
				
				accept(GrammarSymbols.ATRIBUICAO);
				if(this.currentToken.getKind()==GrammarSymbols.NUMERO_INTEIRO||this.currentToken.getKind()==GrammarSymbols.NUMERO_REAL
						||this.currentToken.getKind()==GrammarSymbols.VERDADEIRO||this.currentToken.getKind()==GrammarSymbols.FALSO){
					acceptIt();
				}
				else {
					parseExpressao();
				}
			}
			}
			
		}
		accept(GrammarSymbols.PONTO_VIRGULA);
		return new DeclaracaoVariavel(nome, expAri)
	}
	
	private void parseAtribuicaoVariavel()throws LexicalException,SyntacticException{
		
		if(this.currentToken.getKind()==GrammarSymbols.ID){
			acceptIt();
			parseAtribuicaoVariavel();
			
		}
		else if(this.currentToken.getKind()==GrammarSymbols.ATRIBUICAO){
			acceptIt();
			
			if(this.currentToken.getKind()==GrammarSymbols.NUMERO_INTEIRO||this.currentToken.getKind()==GrammarSymbols.NUMERO_REAL||
				this.currentToken.getKind()==GrammarSymbols.FALSO||this.currentToken.getKind()==GrammarSymbols.VERDADEIRO){
					acceptIt();
					if(this.currentToken.getKind()==GrammarSymbols.PONTO_VIRGULA){
						acceptIt();
					}
					else {
						parseOperador();
						parseExpressao();
						accept(GrammarSymbols.PONTO_VIRGULA);
					}
			}
			else if(this.currentToken.getKind()==GrammarSymbols.PARENTESE_ESQ){
				parseExpressao();
				accept(GrammarSymbols.PONTO_VIRGULA);
			}
			else if(this.currentToken.getKind()==GrammarSymbols.ID){
					acceptIt();
					if(this.currentToken.getKind()==GrammarSymbols.PARENTESE_ESQ){
						parseChamadaFuncao();
					}
					else if(this.currentToken.getKind()==GrammarSymbols.MULTIPLICACAO||
							this.currentToken.getKind()==GrammarSymbols.SOMA||
							this.currentToken.getKind()==GrammarSymbols.SUBTRACAO||
							this.currentToken.getKind()==GrammarSymbols.DIVISAO||
							this.currentToken.getKind()==GrammarSymbols.MAIOR_OU_IGUAL||
							this.currentToken.getKind()==GrammarSymbols.MAIOR_QUE||
							this.currentToken.getKind()==GrammarSymbols.MENOR_OU_IGUAL||
							this.currentToken.getKind()==GrammarSymbols.MENOR_QUE||
							this.currentToken.getKind()==GrammarSymbols.IGUAL_A||
							this.currentToken.getKind()==GrammarSymbols.DIFERENTE){
						parseOperador();
						parseExpressao();
						accept(GrammarSymbols.PONTO_VIRGULA);
						
					}
					else accept(GrammarSymbols.PARENTESE_DIR);
					
				}
			
		}
		
		else if(this.currentToken.getKind()==GrammarSymbols.ATRIBUICAO){
			
			acceptIt();
			parseExpressao();
			
		}
		
		else throw new SyntacticException("Erro sintatico", this.currentToken);
		
		

	}
	private void parseComandoWhile()throws LexicalException,SyntacticException{
		
		if(this.currentToken.getKind()==GrammarSymbols.WHILE){
			
			acceptIt();
			if(this.currentToken.getKind()==GrammarSymbols.PARENTESE_ESQ){
			parseExpressao();
			}
			while(this.currentToken.getKind()!=GrammarSymbols.END){
				
			if(this.currentToken.getKind()==GrammarSymbols.CONTINUE){
					acceptIt();
			}
			else if(this.currentToken.getKind()==GrammarSymbols.EXIT)
				acceptIt();
			else parseComandoSimples();
			}
		}
	}
		
	/*private void parseComando()throws LexicalException,SyntacticException{
		 if(this.currentToken.getKind()==GrammarSymbols.FUNCTION){
			 parseComandoFuncao();
		 }
		 
		 else if(this.currentToken.getKind()==GrammarSymbols.SUBROUTINE){
			 parseComandoSubrotina();
		 }
		 
		 else parseComandoSimples();
	 }*/
	 
	 private void parseParametros()throws LexicalException,SyntacticException{
         
		while(this.currentToken.getKind()!=GrammarSymbols.PARENTESE_DIR){
			 parseValor();
			 if(this.currentToken.getKind()==GrammarSymbols.VIRGULA){
				 acceptIt();
				 if(this.currentToken.getKind()==GrammarSymbols.PONTO_VIRGULA){
					 throw new SyntacticException("Erro Sintatico", this.currentToken);
				 }
			 }
		}	 
 }    
	 
	private void parseComandoSubrotina()throws LexicalException,SyntacticException{
		
		if(this.currentToken.getKind()==GrammarSymbols.SUBROUTINE){
			acceptIt();
			accept(GrammarSymbols.ID);
			accept(GrammarSymbols.PARENTESE_ESQ);
			
			if(this.currentToken.getKind()!=GrammarSymbols.PARENTESE_DIR){
				
				parseParametros();
			}
			
			accept(GrammarSymbols.PARENTESE_DIR);
			
			while(this.currentToken.getKind()!=GrammarSymbols.END){
				parseComandoSimples();
			}
			
			acceptIt();
			accept(GrammarSymbols.SUBROUTINE);
		}
	}
	
	private void parseComandoFuncao()throws LexicalException,SyntacticException{
		
		if(this.currentToken.getKind()==GrammarSymbols.FUNCTION){
			acceptIt();
			accept(GrammarSymbols.ID);
			accept(GrammarSymbols.PARENTESE_ESQ);
			parseParametros();
			accept(GrammarSymbols.PARENTESE_DIR);
			
			while(this.currentToken.getKind()!=GrammarSymbols.END){
				
				parseComandoSimples();
			}
			acceptIt();
			accept(GrammarSymbols.FUNCTION);
		}
	}
	private void parseChamadaFuncao() throws LexicalException,SyntacticException{
		
		if(this.currentToken.getKind()==GrammarSymbols.PARENTESE_ESQ){
			acceptIt();
			parseParametros();
			accept(GrammarSymbols.PARENTESE_DIR);	
			accept(GrammarSymbols.PONTO_VIRGULA);
		}
	}
	
	
	
	private void parseChamadaSubrotina()throws LexicalException,SyntacticException{
		
		if(this.currentToken.getKind()==GrammarSymbols.CALL){
			acceptIt();
			accept(GrammarSymbols.ID);
			accept(GrammarSymbols.PARENTESE_ESQ);
			parseParametros();
			accept(GrammarSymbols.PARENTESE_DIR);
			accept(GrammarSymbols.PONTO_VIRGULA);
		}
	}
	
	private void parseValor() throws LexicalException, SyntacticException{
		
		switch(this.currentToken.getKind()){
		case GrammarSymbols.NUMERO_INTEIRO:
		case GrammarSymbols.NUMERO_REAL:
		case GrammarSymbols.VERDADEIRO:
		case GrammarSymbols.ID:
			acceptIt();
			break;
		default: 
			accept(GrammarSymbols.FALSO);
			break;	
		}
		
		
	}
	
	private void parseExpressao()throws SyntacticException,LexicalException{
		
			
			
			if(this.currentToken.getKind()==GrammarSymbols.PARENTESE_ESQ){
				acceptIt();
				while(this.currentToken.getKind()!=GrammarSymbols.PONTO_VIRGULA&&this.currentToken.getKind()!=GrammarSymbols.PARENTESE_DIR){
					parseValor();
				
					if(this.currentToken.getKind()!=GrammarSymbols.PONTO_VIRGULA&&this.currentToken.getKind()!=GrammarSymbols.PARENTESE_DIR){
						parseOperador();
						parseValor();
					}
					else break;
				
			}
			}		
			else{	parseValor();
					while(this.currentToken.getKind()!=GrammarSymbols.PONTO_VIRGULA&&this.currentToken.getKind()!=GrammarSymbols.PARENTESE_DIR){
					parseOperador();
					parseExpressao();
				}
			}
			
			if(this.currentToken.getKind()==GrammarSymbols.PARENTESE_DIR){
			acceptIt();
			}
	}
	private void parseOperador() throws LexicalException, SyntacticException {
		// TODO Auto-generated method stub
		switch(this.currentToken.getKind()){
		case GrammarSymbols.SOMA:
		case GrammarSymbols.SUBTRACAO:
		case GrammarSymbols.MULTIPLICACAO:
		case GrammarSymbols.DIVISAO:
		case GrammarSymbols.MAIOR_OU_IGUAL:
		case GrammarSymbols.MAIOR_QUE:
		case GrammarSymbols.MENOR_OU_IGUAL:
		case GrammarSymbols.MENOR_QUE:
		case GrammarSymbols.IGUAL_A:
			acceptIt();
			break;
		default: 
			accept(GrammarSymbols.DIFERENTE);
			break;	
		}
	}

	/**
	 * Veririfes if the current token kind is the expected one
	 * @param kind
	 * @throws SyntacticException
	 * @throws LexicalException 
	 */ //TODO
	private void accept(int kind) throws LexicalException,SyntacticException{
		// If the current token kind is equal to the expected
			// Gets next token
		// If not
			// Raises an exception
		
		if(kind == currentToken.getKind()){
	
			currentToken = scanner.getNextToken();
		}
		
		else throw new SyntacticException("Erro Sintatico", this.currentToken);
	}
	
	/**
	 * Gets next token
	 */ //TODO
	private void acceptIt() throws LexicalException {
		currentToken = scanner.getNextToken();
	}
}